﻿using System;
using System.Collections.Generic;
using JSM.DataStore;
using JSM.Internal;

namespace JSM
{
	/// <summary>
	/// This class contains logic to add data for to the JavaScript Model.
	/// </summary>
	internal class JavaScriptModel
	{
		private const string DATASTORE_KEY = "JSM_JavaScriptModel";

		#region Fields

		/// <summary>
		/// The JavaScript variables to process.
		/// </summary>
		private readonly Dictionary<PageLoadEvent, Dictionary<string, object>> _variables;

		/// <summary>
		/// The JavaScript functions to process.
		/// </summary>
		private readonly Dictionary<PageLoadEvent, List<JavaScriptFunctionContainer>> _functions;

		#endregion

		#region Properties

		/// <summary>
		/// Gets a value indicating whether this instance has additional javascript variables.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this instance has javascript variables; otherwise, <c>false</c>.
		/// </value>
		internal bool HasVariables { get { return GetNumberOfVariables() > 0; } }

		/// <summary>
		/// Gets a value indicating whether this instance has javascript functions.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this instance has javascript functions; otherwise, <c>false</c>.
		/// </value>
		internal bool HasFunctions { get { return GetNumberOfFunctions() > 0; } }
		
		#endregion
		
		#region Constructor

		/// <summary>
		/// Initializes a new instance of the <see cref="JavaScriptModel"/> class.
		/// </summary>
		internal JavaScriptModel()
		{
			_variables = new Dictionary<PageLoadEvent, Dictionary<string, object>>();
			_functions = new Dictionary<PageLoadEvent, List<JavaScriptFunctionContainer>>();
		}

		#endregion

		#region Public Methods

		/// <summary>
		/// Adds a variable.
		/// </summary>
		/// <param name="variableName">The variable.</param>
		/// <param name="value">The value.</param>
		/// <param name="pageLoadEvent">The page load event.</param>
		internal void AddVariable(string variableName, object value, PageLoadEvent pageLoadEvent = PageLoadEvent.Ready)
		{
			if (string.IsNullOrWhiteSpace(variableName))
				throw new ArgumentException("The variable name is not set.", "variableName");

			_variables.AddIfNotExist(pageLoadEvent, new Dictionary<string, object>());
			_variables[pageLoadEvent][variableName] = value;
		}

		/// <summary>
		/// Adds multiple variables.
		/// </summary>
		/// <param name="variables">Multiple javascript variables beeing passed as: string, dynamic, string, dynamic ...</param>
		/// <param name="pageLoadEvent">The <see cref="PageLoadEvent"/>.</param>
		internal void AddVariables(PageLoadEvent pageLoadEvent = PageLoadEvent.Ready, params object[] variables)
		{
			if (variables == null)
				throw new ArgumentNullException("variables");

			if (variables.Length == 0)
				throw new ArgumentOutOfRangeException("variables", "Please provide at least one variable");

			if (variables.Length % 2 != 0)
				throw new ArgumentOutOfRangeException("variables", string.Format("The number of parameters must be even. Number of parameters provided: {0}", variables.Length));

			for (int i = 0; i < variables.Length; i += 2)
				AddVariable(Convert.ToString(variables[i]), variables[i + 1], pageLoadEvent);
		}

		/// <summary>
		/// Adds a javascript function.
		/// </summary>
		/// <param name="function">The function.</param>
		/// <param name="pageLoadEvent">The page load event when the function should be executed.</param>
		/// <param name="functionParameter">Optional parameters to pass to the javascript function.</param>
		internal void AddFunction(string function, PageLoadEvent pageLoadEvent = PageLoadEvent.Ready, params object[] functionParameter)
		{
			if (string.IsNullOrWhiteSpace(function))
				throw new ArgumentException("The function to call is not set.", "function");
			
			_functions.AddIfNotExist(pageLoadEvent, new List<JavaScriptFunctionContainer>());
			_functions[pageLoadEvent].Add(new JavaScriptFunctionContainer(function, functionParameter.Length > 0 ? functionParameter : null));
		}
		
		/// <summary>
		/// Inserts the function, to set the function to the first of list
		/// </summary>
		/// <param name="function">The function.</param>
		/// <param name="pageLoadEvent">The load event.</param>
		/// <param name="functionParameter">Optional parameters to pass to the javascript function.</param>
		internal void InsertFunction(string function, PageLoadEvent pageLoadEvent = PageLoadEvent.Ready, params object[] functionParameter)
		{
			if (string.IsNullOrWhiteSpace(function))
				throw new ArgumentException("The function to call is not set.", "function");

			_functions.AddIfNotExist(pageLoadEvent, new List<JavaScriptFunctionContainer>());
			_functions[pageLoadEvent].Insert(0, new JavaScriptFunctionContainer(function, functionParameter.Length > 0 ? functionParameter : null));
		}
		
		#endregion

		#region Static Methods
		internal static JavaScriptModel GetJavaScriptModel(IJavaScriptModelAware javaScriptModelAware = null, IDataStoreProvider dataStoreProvider = null)
		{
			if (dataStoreProvider == null)
			{
				// if no data store provider was passed, try to get the default data store provider for the javaScriptModelAware
				if (javaScriptModelAware == null)
					throw new ArgumentNullException("javaScriptModelAware");
				dataStoreProvider = DefaultDataStoreFactory.GetDefaultDataStore(javaScriptModelAware);
				if (dataStoreProvider == null)
					throw new ArgumentNullException("dataStoreProvider");
			}

			if (!dataStoreProvider.ContainsData(DATASTORE_KEY))
				dataStoreProvider.SetData(DATASTORE_KEY, new JavaScriptModel());
			return (JavaScriptModel)dataStoreProvider.GetData(DATASTORE_KEY);
		}
		#endregion

		#region Non Public Methods

		/// <summary>
		/// Gets the JavaScript variables.
		/// </summary>
		/// <param name="pageLoadEvent">The page load event.</param>
		/// <returns>
		/// The JavaScript variables
		/// </returns>
		internal Dictionary<string, dynamic> GetVariables(PageLoadEvent pageLoadEvent = PageLoadEvent.Ready)
		{
			return _variables.ContainsKey(pageLoadEvent) ? _variables[pageLoadEvent] : null;
		}

		/// <summary>
		/// Gets the additional javascript functions.
		/// </summary>
		/// <param name="pageLoadEvent">The page load event.</param>
		/// <returns>
		/// The javascript functions in a serializable format.
		/// </returns>
		internal List<JavaScriptFunctionContainer> GetFunctions(PageLoadEvent pageLoadEvent = PageLoadEvent.Ready)
		{
			return _functions.ContainsKey(pageLoadEvent) ? _functions[pageLoadEvent] : null;
		}

		/// <summary>
		/// Gets all javascript functions.
		/// </summary>
		/// <returns>
		/// The javascript functions in a serializable format.
		/// </returns>
		internal List<JavaScriptFunctionContainer> GetAllFunctions()
		{
			if (!HasFunctions)
				return null;
			List<JavaScriptFunctionContainer> javaScriptFunctionContainers = new List<JavaScriptFunctionContainer>();
			foreach (KeyValuePair<PageLoadEvent, List<JavaScriptFunctionContainer>> functions in _functions)
				javaScriptFunctionContainers.AddRange(functions.Value);
			return javaScriptFunctionContainers;
		}

		/// <summary>
		/// Gets all javascript variables.
		/// </summary>
		/// <returns>
		/// The javascript variables in a serializable format.
		/// </returns>
		internal Dictionary<string, dynamic> GetAllVariables()
		{
			if (!HasVariables)
				return null;
			Dictionary<string, dynamic> allVariables = new Dictionary<string, dynamic>();
			foreach (KeyValuePair<PageLoadEvent, Dictionary<string, dynamic>> variables in _variables)
				allVariables.AddRange(variables.Value);
			return allVariables;
		}

		/// <summary>
		/// Gets the number of variables.
		/// </summary>
		/// <returns>The numer of variables</returns>
		private uint GetNumberOfVariables()
		{
			return (uint)_variables.Count;
		}

		/// <summary>
		/// Gets the number of javascript functions.
		/// </summary>
		/// <returns></returns>
		private uint GetNumberOfFunctions()
		{
			return (uint)_functions.Count;
		}

		#endregion
	}
}
